﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Loki.Resources;

namespace Loki.Utils
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Rule bug : interface inheritance")]
    public class LokiSettingsService : LokiService, ISettingsComponent
    {
        private List<SettingsConfiguration> _Configurations = new List<SettingsConfiguration>();

        private ISettingsSerializer _Serializer = new BinarySettingsSerializer();

        private ConcurrentDictionary<string, byte[]> _ObjectStore = new ConcurrentDictionary<string, byte[]>();

        public SettingsConfiguration Configure(object P_Target)
        {
            SettingsConfiguration L_Config = FindExistingConfig(P_Target);
            if (L_Config == null)
            {
                L_Config = new SettingsConfiguration(P_Target);
                _Configurations.Add(L_Config);
            }

            return L_Config;
        }

        public void LoadSettings(IDictionary<string, byte[]> P_Settings)
        {
            _ObjectStore.Clear();
            _ObjectStore = new ConcurrentDictionary<string, byte[]>(P_Settings);
        }

        public IDictionary<string, byte[]> GetSettings()
        {
            return _ObjectStore;
        }

        public void ApplyState(object P_Target)
        {
            SettingsConfiguration L_Config = FindExistingConfig(P_Target);

            ISettingsAware L_TrackingAwareTarget = P_Target as ISettingsAware;
            if ((L_TrackingAwareTarget == null) || L_TrackingAwareTarget.OnApplyingState(L_Config))
            {
                foreach (string L_PropertyName in L_Config.Properties)
                {
                    PropertyInfo L_Property = P_Target.GetType().GetProperty(L_PropertyName);
                    string L_PropKey = ConstructPropertyKey(P_Target.GetType().FullName, L_Config.Key, L_Property.Name);
                    try
                    {
                        if (_ObjectStore.ContainsKey(L_PropKey))
                        {
                            object L_StoredValue = _Serializer.Deserialize(_ObjectStore[L_PropKey]);
                            L_Property.SetValue(P_Target, L_StoredValue, null);
                        }
                    }
                    catch (Exception L_Ex)
                    {
                        throw BuildErrorFormat<LokiException>(ErrorMessages.Utils_Settings_SetValueFailed, L_Ex, L_PropKey, L_Property.PropertyType.FullName);
                    }
                }
            }
        }

        public void PersistState(object P_Target)
        {
            SettingsConfiguration L_Config = FindExistingConfig(P_Target);

            ISettingsAware L_TrackingAwareTarget = P_Target as ISettingsAware;
            if ((L_TrackingAwareTarget == null) || L_TrackingAwareTarget.OnPersistingState(L_Config))
            {
                foreach (string L_PropertyName in L_Config.Properties)
                {
                    PropertyInfo L_Property = P_Target.GetType().GetProperty(L_PropertyName);

                    string L_PropKey = ConstructPropertyKey(P_Target.GetType().FullName, L_Config.Key, L_Property.Name);
                    try
                    {
                        object L_CurrentValue = L_Property.GetValue(P_Target, null);
                        _ObjectStore[L_PropKey] = _Serializer.Serialize(L_CurrentValue);
                    }
                    catch (Exception L_Ex)
                    {
                        throw BuildErrorFormat<LokiException>(ErrorMessages.Utils_Settings_GetValueFailed, L_Ex, L_PropKey, L_Property.PropertyType.FullName);
                    }
                }
            }
        }

        #region private helper methods

        private SettingsConfiguration FindExistingConfig(object P_Target)
        {
            return _Configurations.SingleOrDefault(cfg => cfg.TargetReference.Target == P_Target);
        }

        private static string ConstructPropertyKey(string P_TargetTypeName, string P_ObjectKey, string P_PropertyName)
        {
            return string.Format(CultureInfo.InvariantCulture, Formats.Settings_Key, P_TargetTypeName, P_ObjectKey, P_PropertyName);
        }

        #endregion private helper methods
    }
}